﻿namespace TomatoBreak.Infrastructure
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Web.Mvc;

    using ExtensionMethods;
    using Web;

    public static class FilterRegistryHelper
    {
        private static readonly Type genericControllerItemType = typeof(FilterRegistryControllerItem<>);

        public static FilterRegistry Register<TFilter>(this FilterRegistry instance, IEnumerable<Type> controllerTypes, Action<TFilter> configureFilter = null)
            where TFilter : IMvcFilter
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }

            if (controllerTypes == null)
            {
                throw new ArgumentNullException("controllerTypes");
            }

            var controllerTypeList = controllerTypes.ToList();

            EnsureControllerTypes(controllerTypeList);

            foreach (var type in controllerTypeList.Select(controllerType => genericControllerItemType.MakeGenericType(controllerType)))
            {
                instance.ControllerRegistry.Add(Activator.CreateInstance(type, new object[] { CreateAndConfigureFilterFactory(instance, configureFilter) }) as FilterRegistryItem);
            }

            return instance;
        }

        public static FilterRegistry Register<TFilter1, TFilter2>(this FilterRegistry instance, IEnumerable<Type> controllerTypes)
            where TFilter1 : IMvcFilter
            where TFilter2 : IMvcFilter
        {
            return Register(instance, controllerTypes, typeof(TFilter1), typeof(TFilter2));
        }

        public static FilterRegistry Register<TFilter1, TFilter2, TFilter3>(this FilterRegistry instance, IEnumerable<Type> controllerTypes)
            where TFilter1 : IMvcFilter
            where TFilter2 : IMvcFilter
            where TFilter3 : IMvcFilter
        {
            return Register(instance, controllerTypes, typeof(TFilter1), typeof(TFilter2), typeof(TFilter3));
        }

        public static FilterRegistry Register<TFilter1, TFilter2, TFilter3, TFilter4>(this FilterRegistry instance, IEnumerable<Type> controllerTypes)
            where TFilter1 : IMvcFilter
            where TFilter2 : IMvcFilter
            where TFilter3 : IMvcFilter
            where TFilter4 : IMvcFilter
        {
            return Register(instance, controllerTypes, typeof(TFilter1), typeof(TFilter2), typeof(TFilter3), typeof(TFilter4));
        }

        public static FilterRegistry Register<TController, TFilter>(this FilterRegistry instance, Action<TFilter> configureFilter = null)
            where TController : Controller
            where TFilter : IMvcFilter
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }

            instance.Register<TController, IMvcFilter>(CreateAndConfigureFilterFactory(instance, configureFilter));

            return instance;
        }

        public static FilterRegistry Register<TController, TFilter1, TFilter2>(this FilterRegistry instance)
            where TController : Controller
            where TFilter1 : IMvcFilter
            where TFilter2 : IMvcFilter
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }

            return instance.Register<TController, IMvcFilter>(CreateFilterFactories(instance, typeof(TFilter1), typeof(TFilter2)).ToArray());
        }

        public static FilterRegistry Register<TController, TFilter1, TFilter2, TFilter3>(this FilterRegistry instance)
            where TController : Controller
            where TFilter1 : IMvcFilter
            where TFilter2 : IMvcFilter
            where TFilter3 : IMvcFilter
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }

            return instance.Register<TController, IMvcFilter>(CreateFilterFactories(instance, typeof(TFilter1), typeof(TFilter2), typeof(TFilter3)).ToArray());
        }

        public static FilterRegistry Register<TController, TFilter1, TFilter2, TFilter3, TFilter4>(this FilterRegistry instance)
            where TController : Controller
            where TFilter1 : IMvcFilter
            where TFilter2 : IMvcFilter
            where TFilter3 : IMvcFilter
            where TFilter4 : IMvcFilter
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }

            return instance.Register<TController, IMvcFilter>(CreateFilterFactories(instance, typeof(TFilter1), typeof(TFilter2), typeof(TFilter3), typeof(TFilter4)).ToArray());
        }

        public static FilterRegistry Register<TController, TFilter>(this FilterRegistry instance, Expression<Action<TController>> action, Action<TFilter> configureFilter = null)
            where TController : Controller
            where TFilter : IMvcFilter
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }

            return instance.Register(action, CreateAndConfigureFilterFactory(instance, configureFilter));
        }

        public static FilterRegistry Register<TController, TFilter1, TFilter2>(this FilterRegistry instance, Expression<Action<TController>> action)
            where TController : Controller
            where TFilter1 : IMvcFilter
            where TFilter2 : IMvcFilter
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }

            return instance.Register(action, CreateFilterFactories(instance, typeof(TFilter1), typeof(TFilter2)).ToArray());
        }

        public static FilterRegistry Register<TController, TFilter1, TFilter2, TFilter3>(this FilterRegistry instance, Expression<Action<TController>> action)
            where TController : Controller
            where TFilter1 : IMvcFilter
            where TFilter2 : IMvcFilter
            where TFilter3 : IMvcFilter
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }

            return instance.Register(action, CreateFilterFactories(instance, typeof(TFilter1), typeof(TFilter2), typeof(TFilter3)).ToArray());
        }

        public static FilterRegistry Register<TController, TFilter1, TFilter2, TFilter3, TFilter4>(
            this FilterRegistry instance, Expression<Action<TController>> action)
            where TController : Controller
            where TFilter1 : IMvcFilter
            where TFilter2 : IMvcFilter
            where TFilter3 : IMvcFilter
            where TFilter4 : IMvcFilter
        {
            if (instance == null)
            {
                throw new ArgumentNullException("instance");
            }

            return instance.Register(action, CreateFilterFactories(instance, typeof(TFilter1), typeof(TFilter2), typeof(TFilter3), typeof(TFilter4)).ToArray());
        }

        private static FilterRegistry Register(FilterRegistry instance, IEnumerable<Type> controllerTypes, params Type[] filterTypes)
        {
            if (controllerTypes == null)
            {
                throw new ArgumentNullException("controllerTypes");
            }

            var controllerTypeList = controllerTypes.ToList();

            EnsureControllerTypes(controllerTypeList);

            foreach (var type in controllerTypeList.Select(controllerType => genericControllerItemType.MakeGenericType(controllerType)))
            {
                instance.ControllerRegistry.Add(Activator.CreateInstance(type, new object[] { CreateFilterFactories(instance, filterTypes) }) as FilterRegistryItem);
            }

            return instance;
        }

        private static void EnsureControllerTypes(IEnumerable<Type> types)
        {
            foreach (var controllerType in types.Where(controllerType => !typeof(Controller).IsAssignableFrom(controllerType)))
            {
                throw new ArgumentException(ErrorMessages.FilterRegistryHelper_EnsureControllerTypes_Invalid_Type.Interpolate(controllerType.FullName));
            }
        }

        private static IEnumerable<Func<IMvcFilter>> CreateFilterFactories(FilterRegistry registry, params Type[] filterTypes)
        {
            return filterTypes.Select(filterType => new Func<IMvcFilter>(() => registry.Resolver.GetService(filterType) as IMvcFilter));
        }

        private static IEnumerable<Func<IMvcFilter>> CreateAndConfigureFilterFactory<TFilter>(FilterRegistry registry, Action<TFilter> configureFilter)
            where TFilter : IMvcFilter
        {
            return new List<Func<IMvcFilter>>
                       {
                           () =>
                               {
                                   var filter = registry.Resolver.GetService<TFilter>();

                                   if (configureFilter != null)
                                   {
                                       configureFilter(filter);
                                   }

                                   return filter;
                               }
                       };
        }
    }
}